import copy
import itertools
import math
import random

import numpy as np

import Super
import Target
import archiving
import load
import config
import update

particals = config.particals  # 种群数量
MaxIter = config.MaxIter  # 最大迭代次数
time_slot = config.num_days * config.periods_per_day
mutation_rate_s = 0.1
mutation_rate_e = 0.01

'''加入单双周和起始周'''

'''NSGA-II算法实现  SPF  2025.2.17 '''


def NSGAII(task_list, students, teachers, classroom_list, buildings_dis):
    x = []
    y1 = []
    y2 = []
    y3 = []

    # 随机初始化粒子位置
    schemesList, RTmatrix_filled, STmatrix_filled, TTmatrix_filled = load.Load(task_list, students, teachers,
                                                                               classroom_list, particals)
    # 复制种群
    copy_schemesList = copy.deepcopy(schemesList)

    # 计算每个粒子适应度
    fitness = Target.target_fun(RTmatrix_filled, STmatrix_filled, TTmatrix_filled, buildings_dis, len(students),
                                len(teachers))

    # 多目标问题pareto解集
    newParetofrontiers = Super.super(fitness)

    # 选择个体父代
    newParetofrontiers_chian = list(itertools.chain.from_iterable(newParetofrontiers))
    parents = newParetofrontiers_chian[:particals // 2]
    # 对应父代得方案
    parents_index = [row[0] for row in parents]

    particle_g, fitness_g, RTparticle_g = archiving.init_archive(copy_schemesList, newParetofrontiers, RTmatrix_filled)

    # 进行迭代,位置更新，然后计算适应度，更新全局最优
    for t in range(MaxIter):
        update_RTmatrix = []  # 存储此次迭代过程中修改位置后的矩阵列表
        update_schemeslist = []  # 存储迭代过程中修改课程信息后方案的列表
        update_STmatrix_filled = []
        update_TTmatrix_filled = []

        mutation_rate = mutation_rate_s + (mutation_rate_e - mutation_rate_s) * t / MaxIter

        for parent1_index in parents_index:
            # 原有列表不变，进行深拷贝
            matrix_a = copy.deepcopy(RTmatrix_filled[parent1_index])
            update1_ST = copy.deepcopy(STmatrix_filled[parent1_index])
            update1_TT = copy.deepcopy(TTmatrix_filled[parent1_index])
            update1_scheme = copy.deepcopy(copy_schemesList[parent1_index])

            # 随机选择一个不是i的数在parents_index集合中
            parent2_index = random.choice([index for index in parents_index if index != parent1_index])
            matrix_b = copy.deepcopy(RTmatrix_filled[parent2_index])
            update2_ST = copy.deepcopy(STmatrix_filled[parent2_index])
            update2_TT = copy.deepcopy(TTmatrix_filled[parent2_index])
            update2_scheme = copy.deepcopy(copy_schemesList[parent2_index])

            r1 = random.uniform(0, 1)
            r2 = random.randint(1, len(copy_schemesList[parent1_index]))
            r3 = random.random()

            # 遍历所有教学任务
            for index, j in enumerate(copy_schemesList[parent1_index]):
                if index < r2:
                    continue
                task_num = j[0]
                stus = j[6]
                tea = j[3]
                start_week = j[7]
                end_week = j[8]
                week_type = j[11]
                # 在矩阵a中找到task_num教学任务的位置，位置存储在positions_a,找开始周的教室时间片,位置有多个
                positions_a = find_all_element_positions(matrix_a, task_num, start_week)
                positions_b = find_all_element_positions(matrix_b, task_num, start_week)
                if positions_a == positions_b:
                    continue
                count = len(positions_b)
                for k in range(count):
                    if positions_a == positions_b:
                        break
                    # 在最优解附近移动
                    # 交叉
                    trans_position1, trans_position2 = crossover(positions_a[0], positions_b[0], classroom_list,
                                                                 time_slot, r1)
                    # 变异
                    if r3 < mutation_rate:
                        trans_position1, trans_position2 = mutation(trans_position1, trans_position2, classroom_list,
                                                                    time_slot)
                    # 判断教室大小
                    if classroom_list[trans_position1[0]][1] >= len(stus):
                        # 判断冲突
                        conflict1 = is_conflict(matrix_a, update1_ST, update1_TT, trans_position1, start_week, end_week,
                                                week_type, students, teachers, stus, tea)
                        # 如果没有冲突
                        if not conflict1:
                            # 删除矩阵a中删除原来的教学任务
                            # 矩阵a新的位置添加教学任务
                            matrix1_a, update1_ST, update1_TT = update_timetable(matrix_a, update1_ST, update1_TT,
                                                                                 positions_a[0], trans_position1,
                                                                                 start_week, end_week, week_type,
                                                                                 students, teachers, stus, tea,
                                                                                 task_num, classroom_list)
                            # 更新教学任务上课信息
                            update1_task = update.updated_scheme(update1_scheme[index], positions_a[0],
                                                                 trans_position1, classroom_list)
                            # 更新排课方案
                            update1_scheme[index] = update1_task
                    # 判断教室大小---后代二
                    if classroom_list[trans_position2[0]][1] >= len(stus):
                        # 判断冲突
                        conflict2 = is_conflict(matrix_b, update2_ST, update2_TT, trans_position2, start_week, end_week,
                                                week_type, students, teachers, stus, tea)
                        # 如果没有冲突
                        if not conflict2:
                            # 删除矩阵a中删除原来的教学任务
                            # 矩阵a新的位置添加教学任务
                            matrix_b, update2_ST, update2_TT = update_timetable(matrix_b, update2_ST, update2_TT,
                                                                                positions_b[0], trans_position2,
                                                                                start_week, end_week, week_type,
                                                                                students, teachers, stus, tea,
                                                                                task_num, classroom_list)
                            # 更新教学任务上课信息
                            update2_task = update.updated_scheme(update2_scheme[index], positions_b[0],
                                                                 trans_position2, classroom_list)
                            # 更新排课方案
                            update2_scheme[index] = update2_task

                    del positions_a[0]
                    del positions_b[0]
            update_RTmatrix.append(matrix_a)
            update_RTmatrix.append(matrix_b)
            update_schemeslist.append(update1_scheme)
            update_schemeslist.append(update2_scheme)
            update_STmatrix_filled.append(update1_ST)
            update_STmatrix_filled.append(update2_ST)
            update_TTmatrix_filled.append(update1_TT)
            update_TTmatrix_filled.append(update2_TT)
        copy_schemesList = copy.deepcopy(update_schemeslist)
        RTmatrix_filled = copy.deepcopy(update_RTmatrix)
        STmatrix_filled = copy.deepcopy(update_STmatrix_filled)
        TTmatrix_filled = copy.deepcopy(update_TTmatrix_filled)
        # 重新计算适应度值
        fitness___ = Target.target_fun(update_RTmatrix, update_STmatrix_filled, update_TTmatrix_filled, buildings_dis,
                                       len(students), len(teachers))
        # 快速非支配排序、拥挤度计算
        newParetofrontiers = Super.super(fitness___)
        # 更新个体最优
        # 更新种群
        newParetofrontiers_chian = list(itertools.chain.from_iterable(newParetofrontiers))
        parents = newParetofrontiers_chian[:particals // 2]
        # 对应父代得方案
        parents_index = [row[0] for row in parents]
        # 更新全局最优
        particle_g, fitness_g, RTparticle_g = archiving.select_global_best(copy_schemesList, particle_g, fitness_g,
                                                                           newParetofrontiers, update_RTmatrix,
                                                                           RTparticle_g)
        print("迭代次数：", t + 1)
        print("学生教师方差：", fitness_g[1], "教室空闲率：", fitness_g[2], "平均距离：", fitness_g[3])
        x.append(t + 1)
        y1.append(fitness_g[1])
        y2.append(fitness_g[2])
        y3.append(fitness_g[3])
    return particle_g, fitness_g, x, y1, y2, y3, RTparticle_g


def find_all_element_positions(matrix, element, start_week):
    positions = []  # 存储所有值等于 element 的位置
    matrix_arr = np.array(matrix[start_week - 1], object)
    indices = np.array(np.where(matrix_arr != ""))
    for j in range(len(indices[0])):
        if matrix_arr[indices[0][j]][indices[1][j]][0] == element:
            positions.append(tuple(indices[:, j]))
    return positions


def is_conflict(matrix, update_ST, update_TT, position, start_week, end_week, week_type, students, teachers, stus, tea):
    # 冲突
    conflict = False
    room = position[0]
    time_plot = position[1]
    # 检查指定位置是否为空----添加周
    # 连续周
    if week_type == 0:
        for week in range(start_week, end_week + 1):
            # 教室时间冲突检测
            if matrix[week - 1][room][time_plot] != "":
                conflict = True
                break
            # 教师时间冲突
            if update_TT[week - 1][teachers.index(tea)][time_plot] != "":
                conflict = True
                break
            # 学生时间冲突
            for stu in stus:
                if update_ST[week - 1][students.index(stu)][time_plot] != "":
                    conflict = True
                    break
            if conflict:
                break
        return conflict
    # 单周
    if week_type == 1:
        for week in range(start_week, end_week + 1):
            if week % 2 == 1:
                # 教室时间冲突检测
                if matrix[week - 1][room][time_plot] != "":
                    conflict = True
                    break
                # 教师时间冲突
                if update_TT[week - 1][teachers.index(tea)][time_plot] != "":
                    conflict = True
                    break
                # 学生时间冲突
                for stu in stus:
                    if update_ST[week - 1][students.index(stu)][time_plot] != "":
                        conflict = True
                        break
                if conflict:
                    break
        return conflict
    # 双周
    if week_type == 2:
        for week in range(start_week, end_week + 1):
            if week % 2 == 0:
                # 教室时间冲突检测
                if matrix[week - 1][room][time_plot] != "":
                    conflict = True
                    break
                # 教师时间冲突
                if update_TT[week - 1][teachers.index(tea)][time_plot] != "":
                    conflict = True
                    break
                # 学生时间冲突
                for stu in stus:
                    if update_ST[week - 1][students.index(stu)][time_plot] != "":
                        conflict = True
                        break
                if conflict:
                    break
        return conflict


def update_timetable(matrix, update_ST, update_TT, position_a, position_b, start_week, end_week, week_type, students,
                     teachers, stus, tea, task_num, classroom_list):
    # 更新时间表
    if week_type == 0:
        for i in range(start_week, end_week + 1):
            matrix[i - 1][position_a[0]][position_a[1]] = ""
            matrix[i - 1][position_b[0]][position_b[1]] = (task_num, classroom_list[position_b[0]][1], len(stus))
            update_TT[i - 1][teachers.index(tea)][position_a[1]] = ""
            update_TT[i - 1][teachers.index(tea)][position_b[1]] = (task_num, classroom_list[position_b[0]])
            for stu in stus:
                update_ST[i - 1][students.index(stu)][position_a[1]] = ""
                update_ST[i - 1][students.index(stu)][position_b[1]] = (task_num, classroom_list[position_b[0]])
    if week_type == 1:
        for i in range(start_week, end_week + 1):
            if week_type % 2 == 1:
                matrix[i - 1][position_a[0]][position_a[1]] = ""
                matrix[i - 1][position_b[0]][position_b[1]] = (task_num, classroom_list[position_b[0]][1], len(stus))
                update_TT[i - 1][teachers.index(tea)][position_a[1]] = ""
                update_TT[i - 1][teachers.index(tea)][position_b[1]] = (task_num, classroom_list[position_b[0]])
                for stu in stus:
                    update_ST[i - 1][students.index(stu)][position_a[1]] = ""
                    update_ST[i - 1][students.index(stu)][position_b[1]] = (task_num, classroom_list[position_b[0]])
    if week_type == 2:
        for i in range(start_week, end_week + 1):
            if week_type % 2 == 0:
                matrix[i - 1][position_a[0]][position_a[1]] = ""
                matrix[i - 1][position_b[0]][position_b[1]] = (task_num, classroom_list[position_b[0]][1], len(stus))
                update_TT[i - 1][teachers.index(tea)][position_a[1]] = ""
                update_TT[i - 1][teachers.index(tea)][position_b[1]] = (task_num, classroom_list[position_b[0]])
                for stu in stus:
                    update_ST[i - 1][students.index(stu)][position_a[1]] = ""
                    update_ST[i - 1][students.index(stu)][position_b[1]] = (task_num, classroom_list[position_b[0]])
    return matrix, update_ST, update_TT


# 移动位置(交叉)
def crossover(position_a, position_b, classroom_list, time_slot, r1):
    position_a = np.array(position_a)
    position_b = np.array(position_b)
    trans_position1 = np.floor(r1 * position_a + (1 - r1) * position_b)
    trans_position2 = np.floor(r1 * position_b + (1 - r1) * position_a)
    if (trans_position1[0] < 0 and trans_position1[1] < 0) or (
            trans_position1[0] >= len(classroom_list) and trans_position1[1] >= time_slot) or (
            trans_position1[0] < 0 and trans_position1[1] >= time_slot) or (
            trans_position1[0] >= len(classroom_list) and trans_position1[1] < 0):
        trans_position1 = position_a
    if (trans_position1[0] < 0 and 0 <= trans_position1[1] < time_slot) or (
            trans_position1[0] >= len(classroom_list) and 0 <= trans_position1[1] < time_slot):
        trans_position1 = (position_a[0], trans_position1[1])
    if (0 <= trans_position1[0] < len(classroom_list) and trans_position1[1] < 0) or (
            0 <= trans_position1[0] < len(classroom_list) and trans_position1[1] >= time_slot):
        trans_position1 = (trans_position1[0], position_a[1])

    if (trans_position2[0] < 0 and trans_position2[1] < 0) or (
            trans_position2[0] >= len(classroom_list) and trans_position2[1] >= time_slot) or (
            trans_position2[0] < 0 and trans_position2[1] >= time_slot) or (
            trans_position2[0] >= len(classroom_list) and trans_position2[1] < 0):
        trans_position2 = position_a
    if (trans_position2[0] < 0 and 0 <= trans_position2[1] < time_slot) or (
            trans_position2[0] >= len(classroom_list) and 0 <= trans_position2[1] < time_slot):
        trans_position2 = (position_a[0], trans_position2[1])
    if (0 <= trans_position2[0] < len(classroom_list) and trans_position2[1] < 0) or (
            0 <= trans_position2[0] < len(classroom_list) and trans_position2[1] >= time_slot):
        trans_position2 = (trans_position2[0], position_a[1])

    return tuple(map(int, trans_position1)), tuple(map(int, trans_position2))


def mutation(trans_position1, trans_position2, classroom_list, time_slot):
    trans_position1[0] += random.randint(-1, 1)
    trans_position1[1] += random.randint(-1, 1)
    trans_position2[0] += random.randint(-1, 1)
    trans_position2[1] += random.randint(-1, 1)

    if (trans_position1[0] >= len(classroom_list)):
        trans_position1[0] -= 1
    if (trans_position1[0] < 0):
        trans_position1[0] += 1
    if (trans_position1[1] >= time_slot):
        trans_position1[1] -= 1
    if (trans_position1[1] < 0):
        trans_position1[1] += 1
    if (trans_position2[0] >= len(classroom_list)):
        trans_position2[0] -= 1
    if (trans_position2[0] < 0):
        trans_position2[0] += 1
    if (trans_position2[1] >= time_slot):
        trans_position2[1] -= 1
    if (trans_position2[1] < 0):
        trans_position2[1] += 1

    return trans_position1, trans_position2
